import tkinter as tk, os, time, json, shutil, random
from tkinter import messagebox, filedialog, ttk

import re

from protolib import convert_size

class FileBrowser(tk.Frame):
    def __init__(self, parent, controller, json_config, json_config_path):
        super().__init__(parent)
        self.controller = controller

        self.menu_bar = tk.Menu(controller)

        self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
        self.menu_bar.add_cascade(label="File", menu=self.file_menu)
        self.show_hidden_files = tk.BooleanVar(self, json_config['show_hidden_files'] if json_config else False)
        self.file_menu.add_checkbutton(label="Show Hidden Files", variable=self.show_hidden_files)
        self.file_menu.add_separator()
        self.file_menu.add_command(label="Reload Config", command=self.reload_config)
        self.file_menu.add_command(label="Refresh", command=self.on_text_change)
        self.file_menu.add_separator()
        self.file_menu.add_command(label="Exit", command=controller.on_closing)
        self.actions_menu = tk.Menu(self.menu_bar)
        self.menu_bar.add_cascade(label="Actions", menu=self.actions_menu)
        self.actions_menu.add_command(label="Rename", command=self.rename_file)
        self.actions_menu.add_command(label="Copy", command=self.copy_file)
        self.actions_menu.add_command(label="Duplicate", command=self.duplicate_file)
        self.actions_menu.add_command(label="Delete", command=self.delete_file)

        self.json_config = json_config
        self.json_config_path = os.path.abspath(json_config_path)

        self.images = {}
        for file_type in json_config['files']['types']['types']:
            file_type_halfs = file_type.split('/')
            if os.path.exists(f'./images/{file_type_halfs[0]}'):
                if not file_type_halfs[0] in self.images:
                    self.images[file_type_halfs[0]] = {}
            else:
                continue
            if os.path.exists(f'./images/{file_type_halfs[0]}/{file_type_halfs[1]}.png'):
                self.images[file_type_halfs[0]][file_type_halfs[1]] = {"original": tk.PhotoImage(file=f'./images/{file_type_halfs[0]}/{file_type_halfs[1]}.png')}
                self.images[file_type_halfs[0]][file_type_halfs[1]]['16'] = self.images[file_type_halfs[0]][file_type_halfs[1]]["original"].subsample(self.images[file_type_halfs[0]][file_type_halfs[1]]["original"].width() // 16, self.images[file_type_halfs[0]][file_type_halfs[1]]["original"].height() // 16)
            else:
                continue

        self.path_input_frame = tk.Frame(self)
        self.path_input_frame.pack(fill='x')
        self.pathgotoparentdir = tk.Button(self.path_input_frame, text='Go to parent directory', command=self.gotoparentdir)
        self.pathgotoparentdir.pack(side='right')
        self.pathinput = tk.Entry(self.path_input_frame)
        self.pathinput.pack(fill='both', expand=True, side='left')

        self.pathinput.bind("<KeyRelease>", self.on_text_change)

        self.files_list = ttk.Treeview(self, columns=('name', 'type', 'size'))
        self.files_scrollbar = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.files_list.yview)
        self.files_list.configure(yscrollcommand=self.files_scrollbar.set)
        self.files_list.pack(fill=tk.BOTH, expand=tk.YES, side=tk.LEFT)
        self.files_scrollbar.pack(fill=tk.Y, side=tk.RIGHT)

        self.files_list.heading('0', text='Name')
        self.files_list.heading('1', text='Type')
        self.files_list.heading('2', text='Size')

        items = os.listdir(os.getcwd())
        items.sort()
        for index, item in enumerate(items):
            if item.startswith('.') and not self.show_hidden_files.get():
                continue
            file_type = self.identify_file(item)
            self.files_list.insert("", "end", image=self.get_type_icon(file_type, '16'), values=(item,self.json_config['files']['types']['display_names'][file_type], convert_size(os.path.getsize(item)) if os.path.isfile(item) else ""))
        self.pathinput.insert("0", os.path.abspath('.'))

        # Binding the custom on_click function to the list box
        self.last_click_time = 0
        self.files_list.bind("<Button-1>", self.on_click)

    def on_click(self, event):
        current_time = time.time()
        delta = current_time - self.last_click_time
        self.last_click_time = current_time
        if delta < 0.25:
            self.on_double_click(event)
        else:
            self.on_single_click(event)

    def on_single_click(self, event):
        # Single-click behavior goes here
        pass

    def on_double_click(self, event):
        selected_item = self.files_list.item(self.files_list.focus())
        path = str(os.path.realpath(selected_item['values'][0]))
        if os.path.exists(path):
            if os.path.isdir(path):
                self.pathinput.delete(0, tk.END)
                self.pathinput.insert(tk.END, path)
                self.on_text_change(event)
            elif os.path.isfile(path):
                messagebox.showerror('Not Implemented', 'The feature \'Open file\' hasn\'t been implemented')
            else:
                messagebox.showerror('Error', 'That\'s neither a file or directory')
        else:
            messagebox.showerror('Error', 'That does\'t exist. Why does it not exist?')

    def on_text_change(self, event=None):
        self.files_list.delete(*self.files_list.get_children())
        In = self.pathinput.get()

        if os.path.exists(In):
            if os.path.isdir(In):
                os.chdir(In)

        items = os.listdir(os.getcwd())
        items.sort()
        for index, item in enumerate(items):
            if item.startswith('.') and not self.show_hidden_files.get():
                continue
            file_type = self.identify_file(item)
            self.files_list.insert("", "end", image=self.get_type_icon(file_type, '16'), values=(item,self.json_config['files']['types']['display_names'][file_type], convert_size(os.path.getsize(item)) if os.path.isfile(item) else ""))

    def gotoparentdir(self):
        cdir = os.getcwd()
        path = os.path.dirname(cdir)
        self.pathinput.delete(0, tk.END)
        self.pathinput.insert(tk.END, path)
        self.on_text_change(None)

    def identify_file(self, filename):
        if os.path.isfile(filename):
            for pattern_ident in self.json_config['files']['patterns']:
                pattern = self.json_config['files']['patterns'][pattern_ident]
                regex_pattern = pattern['pattern']
                if re.match(regex_pattern, filename, re.IGNORECASE if pattern['ignore_case'] else 0):
                    for type_ident in self.json_config['files']['types']['pattern_associations']:
                        not_mimetype = self.json_config['files']['types']['pattern_associations'][type_ident]
                        if pattern_ident in not_mimetype:
                            return type_ident
            return "type/file"
        elif os.path.isdir(filename):
            return "type/directory"
        else:
            return "type/null"

    def reload_config(self):
        with open(self.json_config_path) as f:
            self.json_config = json.load(f)
            f.close()

    def get_type_icon(self, file_type:str, index:str):
        file_type_halfs = file_type.split('/')
        if file_type_halfs[0] in self.images:
            if file_type_halfs[1] in self.images[file_type_halfs[0]]:
                return self.images[file_type_halfs[0]][file_type_halfs[1]][index]
            else:
                return self.images["type"]["file"][index]
        else:
            return self.images["type"]["null"][index]

    def rename_file(self):
        selected_item = self.files_list.item(self.files_list.focus())
        filename = selected_item['values'][0]
        if len(filename) > 0:
            new_name = ask_string(self.controller, 'Rename File', 'Please enter a new name for the selected file')
            if len(new_name) > 0:
                os.rename(filename, new_name)
                self.on_text_change()
            else:
                messagebox.showerror('Error', 'New file name is empty')
        else:
            messagebox.showerror('Error', 'Nothing selected')

    def copy_file(self):
        selected_item = self.files_list.item(self.files_list.focus())
        filename = selected_item['values'][0]
        if len(filename) > 0:
            destination = filedialog.askdirectory()
            if destination:
                shutil.copy(filename, destination)
                self.on_text_change()
            else:
                messagebox.showerror('Error', 'Destination not specified')
        else:
            messagebox.showerror('Error', 'Nothing selected')

    def duplicate_file(self):
        selected_item = self.files_list.item(self.files_list.focus())
        filename = selected_item['values'][0]
        def get_destination():
            new_destination = f'{filename} {random.randrange(0, 9999)}'
            if os.path.exists(new_destination):
                return get_destination()
            return new_destination
        if len(filename) > 0:
            destination = get_destination()
            if destination:
                shutil.copy(filename, destination)
                self.on_text_change()
            else:
                messagebox.showerror('Error', 'New file name is empty')
        else:
            messagebox.showerror('Error', 'Nothing selected')

    def delete_file(self):
        selected_item = self.files_list.item(self.files_list.focus())
        filename = selected_item['values'][0]
        if len(filename) > 0:
            if messagebox.askokcancel('Confirm destructive action', 'Are you sure you want to delete that file?'):
                os.remove(filename)
                self.on_text_change()
        else:
            messagebox.showerror('Error', 'Nothing selected')

class MainApplication(tk.Tk):
    def __init__(self, file_path, config_path, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title("File Browser - Strange I/O")
        self.minsize(300, 200)

        self.grid_rowconfigure(0, weight=1)  # Make the first row expandable
        self.grid_columnconfigure(0, weight=1)  # Make the first column expandable

        self.container = tk.Frame(self)
        self.container.grid(row=0, column=0, sticky="nsew")
        self.container.grid_rowconfigure(0, weight=1)  # Make the first row of container expandable
        self.container.grid_columnconfigure(0, weight=1)  # Make the first column of container expandable

        self.gui_frames = {}

        if os.path.exists(config_path):
            with open(config_path) as f:
                json_config = json.load(f)
                f.close()
        else:
            json_config = None

        self.add_gui(FileBrowser, json_config, config_path)

        if file_path:
            if os.path.exists(file_path):
                if os.path.isdir(file_path):
                    main_gui = self.gui_frames['FileBrowser']
                    main_gui.pathinput.delete(0, tk.END)
                    main_gui.pathinput.insert(tk.END, os.path.realpath(file_path))
                    main_gui.on_text_change(None)
                else:
                    print(f'\'{file_path}\' isn\'t a directory')
            else:
                print(f'\'{file_path}\' doesn\'t exist')

        self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def add_gui(self, gui_class, *args, **kwargs):
        gui_name = gui_class.__name__
        self.gui_frames[gui_name] = gui_class(self.container, self, *args, **kwargs)

    def show_gui(self, gui_class):
        gui_name = gui_class.__name__
        frame = self.gui_frames[gui_name]
        self.config(menu=frame.menu_bar)
        frame.grid(row=0, column=0, sticky="nsew")
        frame.tkraise()

    def on_closing(self):
        self.quit()
        print("Good by!")

def main(args):
    app = MainApplication(args.file_path, args.config)
    app.show_gui(FileBrowser)
    app.mainloop()

# Will be moved to ProtoLib
def ask_string(controller, title, message):
    win = tk.Toplevel(controller, width=200)
    win.wm_title(title)
    message_label = tk.Label(win, text=message)
    message_label.pack(fill=tk.X)
    text_variable = tk.StringVar(win)
    text_entry = tk.Entry(win, textvariable=text_variable)
    text_entry.pack(fill=tk.X)
    def confirm_command():
        win.destroy()
    confirmation_button = tk.Button(win, text='Confirm', command=confirm_command)
    confirmation_button.pack(fill=tk.X)
    win.resizable(tk.NO, tk.NO)
    controller.wait_window(win)
    return text_variable.get()
